/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jlibrarian;

import jlibrarian.interfaces.BookManager;
import jlibrarian.entities.Book;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
import jlibrarian.entities.Rent;
import jlibrarian.interfaces.RentManager;
import org.apache.derby.client.am.Statement;

/**
 *
 * @author MantaEx
 */
public class BookManagerImpl implements BookManager {

    private final DataSource source;

    /**
     * Constructor with db connection as atribute
     * @param dataSource dataSource with DB connection
     */
    public BookManagerImpl(DataSource dataSource) {
        this.source = dataSource;
    }

    private static Logger log =   Logger.getLogger(BookManagerImpl.class.getName());
    public synchronized Book createBook(Book book) {

        if (book.getAuthors() == null || book.getTitle() == null) {
            throw new NullPointerException("Some of book properties are null");
        }
        if (book.getQuantity() < 0) {
            throw new IllegalArgumentException("book quantity is lower than zero");
        }

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try {
            conn = source.getConnection();
            st = conn.prepareStatement("INSERT INTO books (authors,title,note,quantity) "
                    + "VALUES (?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
            st.setString(1, book.getAuthors());
            st.setString(2, book.getTitle());
            st.setString(3, book.getNote());
            st.setInt(4, book.getQuantity());

            st.executeUpdate();

            rs = st.getGeneratedKeys();

            rs.next();

            book.setBid(rs.getInt(1));
            if(log.isLoggable(Level.INFO)) log.log(Level.INFO, "Created book" + book);

        } catch (SQLException ex) {
            Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new JLibException(ex.getMessage());
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException sqe) {
                    throw new JLibException(sqe);
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                    throw new JLibException(ex);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                    throw new JLibException(ex);
                }
            }
        }
        return book;
    }

    public synchronized boolean deleteBook(Book book) {
        if (book == null) {
            throw new NullPointerException("given parameter book is null");
        }
        boolean b = false;
        Connection conn = null;
        PreparedStatement st = null;
        try {
            conn = source.getConnection();
            
            //delete dependent rents from DB
            this.deleteDependentRents(book);
            
            st = conn.prepareStatement("DELETE FROM books WHERE BID=?");
            st.setInt(1, book.getBid());

            int updated = st.executeUpdate();
            if (updated > 0) {
                b = true;
            }
        } catch (SQLException sqe) {
            b = false;
            throw new JLibException(sqe.getMessage());
        } finally {
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                    throw new JLibException(ex);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException sqe) {
                    throw new JLibException(sqe);
                }
            }
            if(b) if(log.isLoggable(Level.INFO)) log.log(Level.INFO, "Deleted book" + book);
            return b;
        }
    }

    private Book setBorrowed(Book book) {
        RentManager rm = new RentManagerImpl(DbManager.getDataSource());
        book.setBorrowed(rm.findNumberOfCurrentRentsByBook(book));
        return book;
    }

    public synchronized boolean updateBook(Book book) {
        if (book == null) {
            throw new JLibException(new NullPointerException("null parameter book given"));
        }
        if (!(book.getBid() > 0)) {
            throw new JLibException(new IllegalArgumentException("illegal book ID"));
        }

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        boolean success = false;
        int updatedCount = 0;

        try {
            conn = source.getConnection();
            st = conn.prepareStatement("UPDATE books SET authors=?, title=?, note=?, quantity=?  WHERE bid=?");

            st.setString(1, book.getAuthors());
            st.setString(2, book.getTitle());
            st.setString(3, book.getNote());
            st.setInt(4, book.getQuantity());
            st.setInt(5, book.getBid());
            updatedCount = st.executeUpdate();

            if (updatedCount > 0) {
                success = true;
            }
        } catch (SQLException ex) {
            Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new JLibException(ex);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return success;
        }
    }

    public synchronized Collection<Book> findBooksByTitle(String title) {
        return this.findBooksBy("title", title, null, null);
    }

    public synchronized Collection<Book> findBooksByAuthors(String authors) {
        return this.findBooksBy("authors", authors, null, null);
    }

    public synchronized Collection<Book> findBooksByAuthorAndTitle(String authors, String title) {
        if (authors == null) {
            return findBooksByTitle(title);
        }
        if (title == null) {
            return findBooksByAuthors(authors);
        }
        return this.findBooksBy("authors", authors, "title", title);
    }

    public synchronized Collection<Book> getAllBooks() {
        return this.findBooksBy(null, null, null, null);
    }

    public synchronized Book findBookById(int id) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Book book = null;

        try {
            conn = source.getConnection();
            st = conn.prepareStatement("SELECT * FROM books where bid=?");
            st.setInt(1, id);
            rs = st.executeQuery();

            if (rs.next()) {
                book = new Book();
                book.setBid(rs.getInt("bid"));
                book.setAuthors(rs.getString("authors"));
                book.setTitle(rs.getString("title"));
                book.setNote(rs.getString("note"));
                book.setQuantity(rs.getInt("quantity"));
                book = setBorrowed(book);
            }
        } catch (SQLException ex) {
            Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new JLibException(ex);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return book;
        }
    }

    /**
     * This is private method for search in DB
     * @param collumName Name of the collum in DB
     * @param pattern1 text to search in DB
     * @return Collection of results
     */
    private Collection<Book> findBooksBy(String columnName1, String pattern1,
            String columnName2, String pattern2) {
        /*
         * solve some restrictions like minimum is 3 letters... 
         */
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Book book = null;
        List<Book> found = new ArrayList<Book>();

        try {
            conn = source.getConnection();
            /*
             * ex: authors = "pol"
             * search for *pol* => results polacek, krampol, krpole... 
             * all transferred to upper case - case insensitive select
             */
            if (pattern1 != null && pattern2 != null) {
                st = conn.prepareStatement("SELECT * FROM books WHERE UPPER(" + columnName1 + ") LIKE UPPER(?)"
                        + "AND UPPER(" + columnName2 + ") LIKE UPPER(?)");

                st.setString(1, "%" + pattern1 + "%");
                st.setString(2, "%" + pattern2 + "%");
            } else if (pattern1 != null) {
                st = conn.prepareStatement("SELECT * FROM books WHERE UPPER(" + columnName1 + ") LIKE UPPER(?)");
                //why this doesnt work?????
                //st.setString(1, collumName);
                st.setString(1, "%" + pattern1 + "%");
            } else {
                st = conn.prepareStatement("SELECT * FROM books");
            }

            rs = st.executeQuery();

            while (rs.next()) {
                book = new Book();
                book.setBid(rs.getInt("bid"));
                book.setQuantity(rs.getInt("quantity"));
                book.setAuthors(rs.getString("authors"));
                book.setTitle(rs.getString("title"));
                book.setNote(rs.getString("note"));

                found.add(setBorrowed(book));
            }
        } catch (SQLException ex) {
            System.err.println(ex.getMessage());
            throw new JLibException(ex);
        } finally {

            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    Logger.getLogger(BookManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            return found;
        }
    }

    private void deleteDependentRents(Book book) {
        RentManager rm = new RentManagerImpl(source);
        Collection<Rent> toDelete = rm.findRentsByBook(book, true);
        
        for (Rent r : toDelete) {
            rm.deleteRent(r);
        }
    }
}
